/**
 * lib/db.shim
 *
 * @author xiaomi
 */

const lodash = require('lodash');
const Promise = require('bluebird');

const Checkit = require('./checkit.shim');
const Pagination = require('./pagination');

module.exports = function(db) {
    const ModelBase = db.Model;
    const toJSON = ModelBase.prototype.toJSON;

    // checkit.shim
    Checkit.Validator.prototype.unused = function(val, table, column) {
        let qb = db.knex(table);

        qb.where(column, '=', val);

        if(this._target.id) {
            qb.andWhere('id', '!=', this._target.id);
        }

        return qb.then(res => {
            if(res.length) {
                throw new Error(`The ${column}:${val} is already in use.`);
            }
        });
    };

    // export
    db.Pagination = Pagination;

    // PaginationMiddleWare for koa
    db.paginationMiddleWare = function({paginationField = 'X-Pagination'} = {}) {
        return function *(next) {
            yield next;

            let body = this.body;
            if(body && (body instanceof Pagination)) {
                this.set(paginationField, body.exportHeader());

                this.body = body.items;
            }
        };
    };

    // override Model
    db.Model = ModelBase.extend({
        // validate
        initialize: function() {
            if(this.validations && typeof this.validate === 'function') {
                this.on('saving', this.validate, this);
            }
        },
        validate() {
            if(!this.validations) {
                return;
            }

            return Checkit(this.validations).run(this.attributes);
        },

        // safaSet
        safeSet(key, val, options) {
            if(!key) {
                return this;
            }

            let attrs = key;
            if(typeof key === 'string') {
                attrs = { [key]: val };
            }
            else {
                options = val;
            }

            // clean
            const Model = this.constructor;
            attrs = Model.clearAttributes(attrs);

            return this.set(attrs, options);
        },

        // items
        fetchItems(pageOptions, fetchOptions) {
            let _knex;
            let pager = new Pagination(pageOptions.page_size, pageOptions.page_num);

            return this.query(qb => {
                // cache
                _knex = qb.clone();

                // ignore groups
                const ignoreGroups = {
                    columns: 1,
                    order: 1,
                    group: 1
                };

                // filter columns
                qb._statements = qb._statements.filter(st => {
                    return !ignoreGroups[st.grouping];
                });
            })
            .count('*')
            .then(total => {
                pager.setTotal(total);

                let pageNum = pager.page_num;

                return pageNum >= 1 && pageNum <= pager.max_page_num;
            })
            // fetch items
            .then(validated => {
                if(!validated) {
                    return [];
                }

                // reset query
                this._knex = _knex;
                return this.query(qb => {
                    qb.offset((pager.page_num - 1) * pager.page_size);
                    qb.limit(pager.page_size);
                })
                .fetchAll(fetchOptions);
            })
            .then(items => {
                pager.setItems(items);

                return pager;
            });
        },

        // toJSON
        toJSON() {
            let result = toJSON.apply(this, arguments);

            // use unix timestamp output
            return lodash.mapValues(result, val => {
                if(val instanceof Date) {
                    val = Math.floor(val.getTime() / 1000);
                }

                return val;
            });
        }
    }, {
        // static props
        DELETED: 1,
        NOT_DELETED: 0,

        // methdos
        clearAttributes(attrs) {
            if(attrs) {
                attrs = lodash.clone(attrs);

                // clean
                delete attrs.updated_at;
                delete attrs.created_at;
                delete attrs.id;
            }

            return attrs;
        },
        safeForge(attrs, options) {
            attrs = this.clearAttributes(attrs);

            return this.forge(attrs, options);
        },

        // fetchAllByIds
        fetchAllByIds(ids) {
            if(!Array.isArray(ids)) {
                ids = String(ids).split(',').map(id => {
                    return parseInt(id, 10);
                })
                .filter(id => {
                    return id > 0;
                });
            }

            let pager = new Pagination(ids.length || 10, 1);

            if(!ids.length) {
                return Promise.resolve(pager);
            }

            return this.query(qb => {
                qb.where('deleted', this.NOT_DELETED);
                qb.whereIn('id', ids);
            })
            .fetchAll()
            .then(items => {
                pager.setTotal(items.length);
                pager.setItems(items);

                return pager;
            });
        }
    });
    // 挂载事务执行器 executors: generator函数
    db.Model.prototype.buildTransaction = function(excute) {
        return db.transaction(excute);
    };
    return db;
};
